"""Helper class for common operations."""
#!/usr/bin/env python3
import json
import os
import re
import datetime
import platform
import subprocess

UNRELEASED_CHANGELOG_SECTION_TEMPLATE = r"""
## [Unreleased]

### Added


### Changed


### Deprecated


### Removed


### Fixed


### Security


### Obsolete
"""

def get_package_version_from_manifest(package_manifest_path):
    """
    Reads the package.json file and returns the version specified in it.
    """

    if not os.path.exists(package_manifest_path):
        print("get_manifest_json_version function couldn't find a specified manifest_path")
        return None

    with open(package_manifest_path, 'rb') as f:
        json_text = f.read()
        data = json.loads(json_text)

    return data['version']


def update_package_version_by_patch(package_manifest_path):
    """
    Updates the package version in the package.json file.
    This function will bump the package version by a patch.

    The usual usage would be to bump package version during/after release to represent the "current package state" which progresses since the release branch was created
    """

    if not os.path.exists(package_manifest_path):
        raise FileNotFoundError(f"The file {package_manifest_path} does not exist.")

    with open(package_manifest_path, 'r', encoding='UTF-8') as f:
        package_manifest = json.load(f)

    version_parts = get_package_version_from_manifest(package_manifest_path).split('.')
    if len(version_parts) != 3:
        raise ValueError("Version format is not valid. Expected format: 'major.minor.patch'.")

    # Increment the patch version
    version_parts[2] = str(int(version_parts[2]) + 1)
    new_package_version = '.'.join(version_parts)

    package_manifest['version'] = new_package_version

    with open(package_manifest_path, 'w', encoding='UTF-8', newline='\n') as f:
        json.dump(package_manifest, f, indent=4)

    return new_package_version


def regenerate_wrench():
    """
    It runs Tools/regenerate-ci.cmd OR Tools/regenerate-ci.sh script
    to regenerate the CI files. (depending on the OS)
    
    This is needed because wrench scripts content is created dynamically depending on the available editors
    """

    # --- Regenerate the CI files ---
    print("\nRegenerating CI files...")
    script_path = ""
    if platform.system() == "Windows":
        script_path = os.path.join('Tools', 'CI', 'regenerate.bat')
    else: # macOS and Linux
        script_path = os.path.join('Tools', 'CI', 'regenerate.sh')

    if not os.path.exists(script_path):
        print(f"Error: Regeneration script not found at '{script_path}'.")
        return

    try:
        # Execute the regeneration script
        # On non-Windows systems, the script might need execute permissions.
        if platform.system() != "Windows":
            os.chmod(script_path, 0o755)

        print(f"Running '{script_path}'...")
        subprocess.run([script_path], check=True, shell=True)
        print("CI regeneration completed successfully.")

    except subprocess.CalledProcessError as e:
        print(f"Error: The CI regeneration script failed with exit code {e.returncode}.")
    except Exception as e:
        print(f"An unexpected error occurred while running the regeneration script: {e}")
        
        
def update_validation_exceptions(validation_file, package_version):
    """
    Updates the ValidationExceptions.json file with the new package version.
    """

    # If files do not exist, exit
    if not os.path.exists(validation_file):
        return

    # Update the PackageVersion in the exceptions
    with open(validation_file, 'rb') as f:
        json_text = f.read()
        data = json.loads(json_text)
        updated = False
        for exceptionElements in ["WarningExceptions", "ErrorExceptions"]:
            exceptions = data.get(exceptionElements)

            if exceptions is None:
                continue

            for exception in exceptions:
                if 'PackageVersion' in exception:
                    exception['PackageVersion'] = package_version
                    updated = True

    # If no exceptions were updated, we do not need to write the file
    if not updated:
        print(f"No validation exceptions were updated in {validation_file}.")
        return

    with open(validation_file, 'w', encoding='UTF-8', newline='\n') as json_file:
        json.dump(data, json_file, ensure_ascii=False, indent=2)
        json_file.write("\n")  # Add newline cause Py JSON does not
        print(f"updated `{validation_file}`")



def update_changelog(changelog_path, new_version, add_unreleased_template=False):
    """
    Cleans the [Unreleased] section of the changelog by removing empty subsections,
    then replaces the '[Unreleased]' tag with the new version and release date.
    If the version header already exists, it will remove the [Unreleased] section and add any entries under the present version.
    If add_unreleased_template is specified then it will also include the template at the top of the file

    1 - Cleans the [Unreleased] section by removing empty subsections.
    2 - Checks if the version header already has its section in the changelog.
    3 - If it does, it removes the [Unreleased] section and its content.
    4 - If it does not, it replaces the [Unreleased] section with the new version and today's date.
    """

    new_changelog_entry = f'## [{new_version}] - {datetime.date.today().isoformat()}'
    version_header_to_find_if_exists = f'## [{new_version}]'

    with open(changelog_path, 'r', encoding='UTF-8') as f:
        changelog_text = f.read()

    # This pattern finds a line starting with '###', followed by its newline,
    # and then two more lines that contain only whitespace.
    # The re.MULTILINE flag allows '^' to match the start of each line.
    pattern = re.compile(r"^###.*\n\n\n", re.MULTILINE)

    # Replace every match with an empty string. The goal is to remove empty CHANGELOG subsections.
    cleaned_content = pattern.sub('', changelog_text)

    if version_header_to_find_if_exists in changelog_text:
        print(f"A changelog entry for version '{new_version}' already exists. The script will just remove Unreleased section and its content.")
        changelog_text = re.sub(r'(?s)## \[Unreleased(.*?)(?=## \[)', '', changelog_text)
    else:
        # Replace the [Unreleased] section with the new version + cleaned subsections
        print("Latest CHANGELOG entry will be modified to: " + new_changelog_entry)
        changelog_text = re.sub(r'## \[Unreleased\]', new_changelog_entry, cleaned_content)

    # Accounting for the very top of the changelog format
    header_end_pos = changelog_text.find('(https://docs-multiplayer.unity3d.com).', 1)
    insertion_point = changelog_text.find('\n', header_end_pos)

    final_content = ""
    if add_unreleased_template:
        print("Adding [Unreleased] section template to the top of the changelog.")
        final_content = (
            changelog_text[:insertion_point] +
            f"\n{UNRELEASED_CHANGELOG_SECTION_TEMPLATE}" +
            changelog_text[insertion_point:]
        )
    else:
        final_content = (
            changelog_text[:insertion_point] +
            changelog_text[insertion_point:]
        )

    # Write the changes
    with open(changelog_path, 'w', encoding='UTF-8', newline='\n') as file:
        file.write(final_content)
